using System;
using System.Linq;
using System.Linq.Expressions;

namespace Magiq.Support
{
    public class ExpressionChanger : ExpressionVisitor
    {
        private readonly Expression expressionToChange;
        private readonly Expression newExpression;

        public ExpressionChanger(Expression expressionToChange, Expression newExpression)
        {
            this.expressionToChange = expressionToChange;
            this.newExpression = newExpression;
        }

        protected override Expression VisitConstant(ConstantExpression expression)
        {
            return expression == expressionToChange ? newExpression : expression;
        }

        protected override Expression VisitParameter(ParameterExpression expression)
        {
            return expression;
        }

        protected override Expression VisitLambda(LambdaExpression expression)
        {
            return Expression.Lambda(Visit(expression.Body), expression.Parameters.ToArray());
        }

        protected override Expression VisitNewArrayBounds(NewArrayExpression expression)
        {
            return Expression.NewArrayBounds(expression.Type, VisitMany(expression.Expressions));
        }

        protected override Expression VisitNewArrayInit(NewArrayExpression expression)
        {
            return Expression.NewArrayInit(expression.Type, VisitMany(expression.Expressions));
        }

        protected override MemberBinding Binding(MemberBinding binding)
        {
            switch (binding.BindingType)
            {
                case MemberBindingType.Assignment:
                    return Expression.Bind(binding.Member, ((MemberAssignment) binding).Expression);
                case MemberBindingType.MemberBinding:
                    return Expression.MemberBind(binding.Member,
                                                 ((MemberMemberBinding) binding).Bindings.Select(Binding));
                case MemberBindingType.ListBinding:
                    return Expression.ListBind(binding.Member,
                                               ((MemberListBinding) binding).Initializers.Select(
                                                   VisitElementInit));
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        protected override Expression VisitMemberInit(MemberInitExpression expression)
        {
            return Expression.MemberInit((NewExpression) VisitNew(expression.NewExpression), expression.Bindings.Select(Binding));
        }

        protected override Expression VisitListInit(ListInitExpression expression)
        {
            return Expression.ListInit((NewExpression) VisitNew(expression.NewExpression),
                                       expression.Initializers
                                           .Select(VisitElementInit));
        }

        protected override ElementInit VisitElementInit(ElementInit elementInit)
        {
            return Expression.ElementInit(elementInit.AddMethod, VisitMany(elementInit.Arguments));
        }

        protected override Expression VisitNew(NewExpression expression)
        {
            return Expression.New(expression.Constructor, VisitMany(expression.Arguments), expression.Members);
        }


        protected override Expression VisitInvoke(InvocationExpression expression)
        {
            return Expression.Invoke(Visit(expression.Expression),
                                     expression.Arguments.Select(Visit));
        }

        protected override Expression VisitConditional(ConditionalExpression expression)
        {
            return Expression.Condition(Visit(expression.Test),
                                        Visit(expression.IfTrue),
                                        Visit(expression.IfFalse));
        }

        protected override Expression VisitBinary(BinaryExpression expression)
        {
            return Expression.MakeBinary(expression.NodeType,
                                         Visit(expression.Left),
                                         Visit(expression.Right),
                                         expression.IsLiftedToNull,
                                         expression.Method,
                                         expression.Conversion);
        }

        protected override Expression VisitUnary(UnaryExpression expression)
        {
            return Expression.MakeUnary(expression.NodeType,
                                        Visit(expression.Operand), expression.Type,
                                        expression.Method);
        }

        protected override Expression VisitMember(MemberExpression expression)
        {
            return expression == expressionToChange ? newExpression : Expression.MakeMemberAccess(Visit(expression.Expression), expression.Member);
        }

        protected override Expression VisitTypeIs(TypeBinaryExpression expression)
        {
            return Expression.TypeIs(Visit(expression.Expression), expression.TypeOperand);
        }

        protected override Expression VisitCall(MethodCallExpression expression)
        {
            return expression == expressionToChange ? newExpression : Expression.Call(Visit(expression.Object), expression.Method, expression.Arguments);
        }
    }
}